
/*
 *  Copyright 2007 Marc Lanctot and Marc Gendron-Bellemare
 *
 *  This file is part of OpenMTG.
 *
 *  OpenMTG is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  OpenMTG is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenMTG.  If not, see <http://www.gnu.org/licenses/>.
 *
 */



import java.io.*;
import java.net.*;
import java.util.*;


public class ClientHandler extends Thread
{
  // The client ID number. Will always be equal to the player ID number of the player
  // that corresponds to this client. 
  private int num;
  
  private PrintStream out; 
  private BufferedReader in; 
  private Server server;
  private Socket socket;
  private Player player;
  private boolean alive;

  public ClientHandler(int num, PrintStream out, BufferedReader in, Server server, Socket socket)
  {
    this.num = num;
    this.out = out;
    this.in = in;
    this.server = server;
    this.socket = socket;
    this.alive = true;
  }

  private void print(String str)
  {
    System.out.println("## CH" + num + ": " + str);
  }

  public synchronized void kill()
  {
    alive = false;
    send("KILL");
    cleanup();
  }
  
  private synchronized void cleanup()
  {
    try {
      out.close();
      in.close();
      socket.close();
    }
    catch(Exception e) { throw new RuntimeException(e); }
  }

  public void run()
  {
    print("Starting client handler..."); 
  
    String inputLine = "";

    while (alive)
    {
			// Whoever's turn it is makes the server run through the phases
			if (player != null && server.isGameOn())
			{
				//if (player.getId() == server.getTurnPlayerId())
				// Note! ANY player will walk through the phase steps... this
				//  method MUST be sychronized and NOT rely on the client handler
				//  who called it
				while (!server.actPhase()) ;

				if (server.isEndTurn())
					server.endTurn();
			}		

			// MGB: disabled non-IO exception here to make for easier debugging
      try { 
        inputLine = in.readLine();
        
        if (!alive)
          break;
        
        print("Received line " + inputLine); 
        handleLine(inputLine);
      }
      catch(IOException e) 
      { if (alive) throw new RuntimeException(e); }
    }
  }

  // communication
  
  public void send(String s) {
    out.println(s);
  }

  // handlers

  private void handleLine(String line) 
  {
		String[] args = line.split(" ");
		if (args.length < 1) return;
		
		String cmd = args[0];
		
    if (cmd.equals("PLAYERINFO"))
      handlePlayerInfo(line);
    else if (cmd.equals("DECKINFO"))
      handleDeckInfo(line);
    else if (cmd.equals("GAMESTART"))
      handleGameStart(line);
    else if (cmd.equals("MULLIGAN"))
      handleMulligan(line);
    else if (cmd.equals("FIRSTTURN"))
      handleFirstTurn(line);
    else if (cmd.equals("PLAYLAND"))
      handlePlayLand(line);
    else if (cmd.equals("TAPFORMANA"))
      handleTapForMana(line);
    else if (cmd.equals("CAST"))
      handleCast(line);
    else if (cmd.equals("ATTACK"))
      handleAttack(line);
    else if (cmd.equals("BLOCK"))
      handleBlock(line);
    else if (cmd.equals("GO"))
      handleGo(line);
    else if (cmd.equals("D"))
      handleD(line);
    else if (cmd.equals("NEXT"))
      handleNext(line);
    else if (cmd.equals("STARTTURN"))
			handleStartTurn(line);
		else if (cmd.equals("DISCARD"))
			handleDiscard(line);
    else if (cmd.equals("AD"))
      handleAssignDamage(line);
		else
      System.out.println("Unknown message. Can't handle."); 
  }

  private void handlePlayerInfo(String line)
  {
    String[] parts = line.split(" "); 
    String name = parts[1];

    // player id is same as client id
    player = new Player(num, name);  

    int errVal = server.addPlayer(player);
    
    if (errVal == 1) {      
      send("ERROR Too many players");
      server.removeClient(this);
      return;
    }
    else if (errVal == 2) {
      send("ERROR Duplicate name");
      server.removeClient(this);
      return;
    }

    print("Added player name=" + name + ",id=" + num);

    send("OK playerinfo");
  }

  private void handleDeckInfo(String line)
  {
    String[] parts = line.split(" "); 
    String deckname = parts[1];

    Deck deck = new Deck("decks/" + deckname + ".deck"); 

    if (!server.setDeck(num, deck)) {      
      send("ERROR setting deck"); 
      return;
    }

    print("Added deck (" + deckname + ") , playerid=" + num);

    send("OK deckinfo");

    server.checkStart(); 
  }

  private void handleGameStart(String line)
  {
    String[] parts = line.split(" "); 
    int whoStarts = Integer.parseInt(parts[1]);

    if (server.whoDecides() != num) {      
      send("ERROR wrong client");
      return;
    }

    server.setWhoStarts(whoStarts);
  }

  private void handleMulligan(String line)
  {
    if (server.getTurn() >= 1) {
      send("ERROR turn >= 1"); 
      return;
    }

    if (!server.mulligan(player.getId()))
      send("ERROR state.mulligan() failed");

    server.broadcast("MSG Player " + player.getName() + " chose to mulligan. "); 

    send("MULLIGAN 1");

    server.sendHand(player.getId());

    send("MULLIGAN 2");

    server.broadcast("PLAYERINFO " + player.getInfo());
  }

  private void handleFirstTurn(String line)
  {
    server.broadcast("MSG " + player.getName() + " is ready to start."); 
    server.firstTurnRequest(); 
  }

  private void handlePlayLand(String line)
  {
    String[] parts = line.split(" ");
    int iid = Integer.parseInt(parts[1]); 

		// Make sure this is the main phase
		if (!server.isMainPhase())
		{
			send ("ERROR playland failed");
			return;
		}
		
    Card c = server.playLand(player.getId(), iid);

    if (c == null ) {
      send("ERROR playland failed");
      return;
    }

    server.broadcast("PLAYLAND " + player.getId() + " " + c.encode()); 
  }


  private void handleTapForMana(String line)
  {
    String[] parts = line.split(" ");
    ArrayList<Integer> iids = new ArrayList<Integer>();
   
    boolean tapped = false;

    for (int i = 1; i < parts.length; i++)
    {
      int iid = Integer.parseInt(parts[i]); 

      if (!server.tapForMana(num, iid))
        send("ERROR tapping for mana, iid=" + iid);
      else
      {
        iids.add(iid);
        tapped = true;
      }
    }

    if (tapped)
      server.broadcast("TAPFORMANA " + num + " " + Helpers.join(iids, " "));
  }

  private void handleCast(String line)
  {
    String[] parts = line.split(" ");
    int iid = Integer.parseInt(parts[1]);

    // get target ids..
    Card c = server.cast(num, iid);

		String cardText;
	
    if (c == null)
      send("ERROR cast failed");
		else
		{
			cardText = c.encode();
    	server.broadcast("CAST " + num + " " + cardText + " " + player.manaPool); 
		}
  }
  
  private void handleAttack(String line)
  {
    String[] parts = line.split(" ");
    
    if (parts.length < 2) {
      send("ERROR not enough arguments");
      return;
    }
      
    // Check for automatically determining opponent id 
    int start_index = 2;
    int d_pid = Integer.parseInt(parts[1]);
    if (server.getPlayers() == 2) {
      start_index = 1;
      d_pid = (player.getId() == 0 ? 1 : 0);
    }
      
    ArrayList<Integer> iids = new ArrayList<Integer>();
  
    for (int i = start_index; i < parts.length; i++)
    {
      int iid = Integer.parseInt(parts[i]); 

      if (!server.attack(num, d_pid, iid))
        send("ERROR attacking, d_pid="+ d_pid + ", iid=" + iid);
      else
        iids.add(iid);
    }

	// @@@ this seems incorrect
    server.broadcast("ATTACK " + num + " " + d_pid + " " + Helpers.join(iids, " "));
  }
  
  private void handleBlock(String line)
  {
		String[] args = line.split(" ");
		if (args.length < 3)
		{
			send("ERROR blocking - not enough arguments");
			return;
		}

		Integer attackerId = Integer.parseInt(args[1]);
    Integer blockerId = Integer.parseInt(args[2]);
    // must add support for multiple blockers.. ids will be in args[3] and beyond

		if (!server.block(player.getId(), blockerId, attackerId))
		{
			send ("ERROR blocking "+player.getId()+" "+blockerId + " "+attackerId);
			return;
		}
		
		server.broadcast("MSG Blocker " + blockerId + " assigned to block " + attackerId);
  }
  
  private void handleGo(String line)
  {
		server.go(num);
  }
  
  private void handleD(String line)
  {
    if (!server.d_available())
    {
      send("ERROR no cast stack");
      return;
    }
    
    boolean resolved = server.endStackRequest(num);
    if (!resolved)
      send("D Ok.");
  }

  private void handleNext(String line)
  {
    // Skip to whatever is next, based on whether there is a cast stack
    if (server.d_available())
      handleD(null);
    else
      handleGo(null);
  }
  
	private void handleStartTurn(String line)
	{
	  // Do nothing - simply let the server cycle through phases
		return;
	}

	private void handleDiscard(String line)
	{
		String[] args = line.split(" ");
		if (args.length < 2)
		{
			send("ERROR Discard what?");
			return;
		}
		
		ArrayList<Integer> list = new ArrayList<Integer>();

		for (int i = 1; i < args.length; i++)
			list.add(new Integer(args[i]));
			
		server.discard(num, list);
	}
	
  private void handleAssignDamage(String line)
  {
    String[] args = line.split(" ");
    if (args.length < 4)
    {
      send("ERROR Assign damage to who? (from who?)");
      return;
    }
 
    // format is AD <caller pid> <to id> <dmg> [from id]
    
    int pid = Integer.parseInt(args[1]);
    int toid = Integer.parseInt(args[2]);
    int dmg = Integer.parseInt(args[3]);
    int from = -1;
    
    if (args.length >= 5)
      from = Integer.parseInt(args[4]);
    
    if (!server.assignDamage(pid, toid, from, dmg))
    {
      send("ERROR Execution of assign damage failed");
      return;
    }
    
    server.broadcast("MSG Player" + pid + " assigns " + dmg + " damage to " + toid);
  }
}


